#include <stdio.h>
#include <string.h>
#include <ctype.h>
#include "defs.h"
#include "externs.h"
#include "protos.h"

unsigned char auto_inc;
unsigned char auto_tag;
unsigned int  auto_tag_value;


/* ----
 * class1()
 * ----
 * 1 byte, no operand field
 */

void
class1(int *ip)
{
    check_eol(ip);

    /* update location counter */
    loccnt++;

    /* generate code */
    if (pass == LAST_PASS) {
        /* opcode */
        putbyte(data_loccnt, opval);

        /* output line */
        println();
    }
}


/* ----
 * class2()
 * ----
 * 2 bytes, relative addressing
 */

void
class2(int *ip)
{
    unsigned int addr;

    /* update location counter */
    loccnt += 2;

    /* get destination address */
    if (!evaluate(ip, ';'))
        return;

    /* generate code */
    if (pass == LAST_PASS) {
        /* opcode */
        putbyte(data_loccnt, opval);

        /* calculate branch offset */
        addr = value - (loccnt + (page << bank_bits));

        /* check range */
        if (addr > 0x7F && addr < 0xFFFFFF80) {
            error("Branch address out of range!");
            return;
        }

        /* offset */
        putbyte(data_loccnt+1, addr);

        /* output line */
        println();
    }
}


/* ----
 * class3()
 * ----
 * 2 bytes, inherent addressing
 */

void
class3(int *ip)
{
    check_eol(ip);

    /* update location counter */
    loccnt += 2;

    /* generate code */
    if (pass == LAST_PASS) {
        /* opcodes */
        putbyte(data_loccnt, opval);
        putbyte(data_loccnt+1, optype);

        /* output line */
        println();
    }
}


/* ----
 * class4()
 * ----
 * various addressing modes
 */

void
class4(int *ip)
{
    char buffer[32];
    unsigned char c;
    int len, mode;
    int    i;

    /* skip spaces */
    while (isspace(prlnbuf[*ip]))
        (*ip)++;

    /* low/high byte prefix string */
    if (isalpha(prlnbuf[*ip])) {
        len = 0;
        i = *ip;

        /* extract string */
        for (;;) {
            c = prlnbuf[i];
            if (c == '\0' || c == ' ' || c == '\t' || c == ';')
                break;
            if ((!isalpha(c) && c != '_') || (len == 31)) {
                len = 0;
                break;
            }
            buffer[len++] = c;
            i++;
        }

        /* check */
        if (len) {
            buffer[len] = '\0';

            if (_stricmp(buffer, "low_byte") == 0) {
                opext = 'L';
                *ip = i;
            }
            if (_stricmp(buffer, "high_byte") == 0) {
                opext = 'H';
                *ip = i;
            }
        }
    }

    /* get operand */
    mode = getoperand(ip, opflg, ';');
    if (!mode)
        return;

    /* make opcode */
    if (pass == LAST_PASS) {
        for (i = 0; i < 32; i++) {
            if (mode & (1 << i))
                break;
        }
        opval += opvaltab[optype][i];
    }

    /* auto-tag */
    if (auto_tag) {
        if (pass == LAST_PASS) {
            putbyte(loccnt, 0xA0);
            putbyte(loccnt+1, auto_tag_value);
        }
        loccnt += 2;
    }

    /* generate code */
    switch(mode) {
    case ACC:
        /* one byte */
        if (pass == LAST_PASS)
            putbyte(loccnt, opval);

        loccnt++;
        break;

    case IMM:
    case ZP:
    case ZP_X:
    case ZP_Y:
    case ZP_IND:
    case ZP_IND_X:
    case ZP_IND_Y:
        /* two bytes */
        if (pass == LAST_PASS) {
            putbyte(loccnt, opval);
            putbyte(loccnt+1, value);
        }
        loccnt += 2;
        break;

    case ABS:
    case ABS_X:
    case ABS_Y:
    case ABS_IND:
    case ABS_IND_X:
        /* three bytes */
        if (pass == LAST_PASS) {
            putbyte(loccnt, opval);
            putword(loccnt+1, value);
        }
        loccnt += 3;
        break;
    }

    /* auto-increment */
    if (auto_inc) {
        if (pass == LAST_PASS)
            putbyte(loccnt, auto_inc);

        loccnt += 1;
    }

    /* output line */
    if (pass == LAST_PASS)
        println();
}


/* ----
 * class5()
 * ----
 * 3 bytes, zp/relative addressing
 */

void
class5(int *ip)
{
    int    zp;
    unsigned int addr;
    int mode;

    /* update location counter */
    loccnt += 3;

    /* get first operand */
    mode = getoperand(ip, ZP, ',');
    zp   = value;
    if (!mode)
        return;

    /* get second operand */
    mode = getoperand(ip, ABS, ';');
    if (!mode)
        return;

    /* generate code */
    if (pass == LAST_PASS) {
        /* opcodes */
        putbyte(data_loccnt, opval);
        putbyte(data_loccnt+1, zp);

        /* calculate branch offset */
        addr = value - (loccnt + (page << bank_bits));

        /* check range */
        if (addr > 0x7F && addr < 0xFFFFFF80) {
            error("Branch address out of range!");
            return;
        }

        /* offset */
        putbyte(data_loccnt+2, addr);

        /* output line */
        println();
    }
}


/* ----
 * class6()
 * ----
 * 7 bytes, src/dest/length
 */

void
class6(int *ip)
{
    int    i;
    int addr[3];

    /* update location counter */
    loccnt +=7;

    /* get operands */
    for (i = 0; i < 3; i++) {
        if (!evaluate(ip, (i < 2) ? ',' : ';'))
            return;
        if (pass == LAST_PASS) {
            if (value & 0xFFFF0000) {
                error("Operand size error!");
                return;
            }
        }
        addr[i] = value;
    }

    /* generate code */
    if (pass == LAST_PASS) {
        /* opcodes */
        putbyte(data_loccnt, opval);
        putword(data_loccnt+1, addr[0]);
        putword(data_loccnt+3, addr[1]);
        putword(data_loccnt+5, addr[2]);

        /* output line */
        println();
    }
}


/* ----
 * class7()
 * ----
 * TST instruction
 */

void
class7(int *ip)
{
    int mode;
    int addr, imm;

    /* get first operand */
    mode = getoperand(ip, IMM, ',');
    imm  = value;
    if (!mode)
        return;

    /* get second operand */
    mode = getoperand(ip, (ZP | ZP_X | ABS | ABS_X), ';');
    addr = value;
    if (!mode)
        return;

    /* make opcode */
    if (mode & (ZP | ZP_X))
        opval = 0x83;
    if (mode & (ABS | ABS_X))
        opval = 0x93;
    if (mode & (ZP_X | ABS_X))
        opval+= 0x20;

    /* generate code */
    if (pass == LAST_PASS) {
        /* opcodes */
        putbyte(loccnt, opval);
        putbyte(loccnt+1, imm);

        if (mode & (ZP | ZP_X))
            /* zero page */
            putbyte(loccnt+2, addr);
        else
            /* absolute */
            putword(loccnt+2, addr);
    }

    /* update location counter */ 
    if (mode & (ZP | ZP_X))
        loccnt += 3;
    else
        loccnt += 4;

    /* auto-increment */
    if (auto_inc) {
        if (pass == LAST_PASS)
            putbyte(loccnt, auto_inc);

        loccnt += 1;
    }

    /* output line */
    if (pass == LAST_PASS)
        println();
}


/* ----
 * class8()
 * ----
 * TAM/TMA instruction
 */

void
class8(int *ip)
{
    int mode;

    /* update location counter */
    loccnt += 2;

    /* get operand */
    mode = getoperand(ip, IMM, ';');
    if (!mode)
        return;

    /* generate code */
    if (pass == LAST_PASS) {
        /* check page index */
        if (value & 0xF8) {
            error("Incorrect page index!");
            return;
        }

        /* opcodes */
        putbyte(data_loccnt, opval);
        putbyte(data_loccnt+1, (1 << value));

        /* output line */
        println();
    }
}


/* ----
 * class9()
 * ----
 * RMB/SMB instructions
 */

void
class9(int *ip)
{
    int bit;
    int mode;

    /* update location counter */
    loccnt += 2;

    /* get the bit index */
    mode = getoperand(ip, IMM, ',');
    bit  = value;
    if (!mode)
        return;

    /* get the zero page address */
    mode = getoperand(ip, ZP, ';');
    if (!mode)
        return;

    /* generate code */
    if (pass == LAST_PASS) {
        /* check bit number */
        if (bit > 7) {
            error("Incorrect bit number!");
            return;
        }

        /* opcodes */
        putbyte(data_loccnt, opval + (bit << 4));
        putbyte(data_loccnt+1, value);

        /* output line */
        println();
    }
}


/* ----
 * class10()
 * ----
 * BBR/BBS instructions
 */

void
class10(int *ip)
{
    int bit;
    int zp;
    int mode;
    unsigned int addr;

    /* update location counter */
    loccnt += 3;

    /* get the bit index */
    mode = getoperand(ip, IMM, ',');
    bit  = value;
    if (!mode)
        return;

    /* get the zero page address */
    mode = getoperand(ip, ZP, ',');
    zp   = value;
    if (!mode)
        return;

    /* get the jump address */
    mode = getoperand(ip, ABS, ';');
    if (!mode)
        return;

    /* generate code */
    if (pass == LAST_PASS) {
        /* check bit number */
        if (bit > 7) {
            error("Incorrect bit number!");
            return;
        }

        /* opcodes */
        putbyte(data_loccnt, opval + (bit << 4));
        putbyte(data_loccnt+1, zp);

        /* calculate branch offset */
        addr = value - (loccnt + (page << bank_bits));

        /* check range */
        if (addr > 0x7F && addr < 0xFFFFFF80) {
            error("Branch address out of range!");
            return;
        }

        /* offset */
        putbyte(data_loccnt+2, addr);

        /* output line */
        println();
    }
}


/* ----
 * getoperand()
 * ----
 */

int
getoperand(int *ip, int flag, int last_char)
{
    unsigned int tmp;
    unsigned char c;
    int code;
    int mode;
    int pos;
    int end;

    char val_buf[256] = { 0 };

    /* init */
    auto_inc = 0;
    auto_tag = 0;

    /* skip spaces */
    while (isspace(prlnbuf[*ip]))
        (*ip)++;

    /* check addressing mode */
    switch (prlnbuf[*ip]) {
    case '\0':
        if (ACC & flag)
        {
            c = prlnbuf[(*ip) + 1];
            if (isspace(c) || c == '\0' || c == ';' || c == ',') {
                mode = ACC;
                (*ip)++;
                break;
            }
        }
        /* no operand */
        error("Operand missing!");
        return (0);
    case ';':
        /* no operand */
        error("Operand missing!");
        return (0);

    case 'A':
    case 'a':
        /* accumulator */
        c = prlnbuf[(*ip)+1];
        if (isspace(c) || c == '\0' || c == ';' || c == ',') {
            mode = ACC;
            (*ip)++;
            break;
        }

    default:
        /* other */
        if (0 == strncmp(&prlnbuf[*ip], "#<", 2)) {
            strcpy_s(val_buf, sizeof(val_buf), "#LOW(");
            strcat_s(val_buf, sizeof(val_buf), &prlnbuf[*ip + 2]);
            strcat_s(val_buf, sizeof(val_buf), ")");
            strcpy_s(&prlnbuf[*ip], sizeof(val_buf), val_buf);
            mode = IMM;
        }

        if (0 == strncmp(&prlnbuf[*ip], "#>", 2)) {
            strcpy_s(val_buf, sizeof(val_buf), "#HIGH(");
            strcat_s(val_buf, sizeof(val_buf), &prlnbuf[*ip + 2]);
            strcat_s(val_buf, sizeof(val_buf), ")");
            strcpy_s(&prlnbuf[*ip], sizeof(val_buf), val_buf);
            mode = IMM;
        }

        if (0 == strncmp(&prlnbuf[*ip], "<", 1)) {

            unsigned int op_value = 0;
            int ip_tmp = *ip + 1;
            if (evaluate(&ip_tmp, 0))
            {
                op_value = value;
            }

            int beg = *ip + 1;
            int end = beg;

            while ('\0' != prlnbuf[end]) {
                unsigned char ch = prlnbuf[end];
                if (';' == ch || ',' == ch) {
                    break;
                }

                end++;
            }

            if (op_value < 0x0100)
            {
                strcpy_s(val_buf, sizeof(val_buf), "LOW(");
                strncat_s(val_buf, sizeof(val_buf), &prlnbuf[*ip + 1], end - beg);
                strcat_s(val_buf, sizeof(val_buf), ")");
                strcat_s(val_buf, sizeof(val_buf), &prlnbuf[end]);
            }
            else
            {
                strncat_s(val_buf, sizeof(val_buf), &prlnbuf[*ip + 1], end - beg);
                strcat_s(val_buf, sizeof(val_buf), &prlnbuf[end]);
            }

            strcpy_s(&prlnbuf[*ip], sizeof(val_buf), val_buf);
            mode = ZP;
        }

        if (0 == strncmp(&prlnbuf[*ip], ">", 1)) {

            unsigned int op_value = 0;
            int ip_tmp = *ip + 1;
            if (evaluate(&ip_tmp, 0))
            {
                op_value = value;
            }

            int beg = *ip + 1;
            int end = beg;

            while ('\0' != prlnbuf[end]) {
                unsigned char ch = prlnbuf[end];
                if (';' == ch || ',' == ch) {
                    break;
                }

                end++;
            }

            if (op_value < 0x0100)
            {
                strcpy_s(val_buf, sizeof(val_buf), "HIGH(");
                strncat_s(val_buf, sizeof(val_buf), &prlnbuf[*ip + 1], end - beg);
                strcat_s(val_buf, sizeof(val_buf), ")");
                strcat_s(val_buf, sizeof(val_buf), &prlnbuf[end]);
            }
            else
            {
                strncat_s(val_buf, sizeof(val_buf), &prlnbuf[*ip + 1], end - beg);
                strcat_s(val_buf, sizeof(val_buf), &prlnbuf[end]);
            }

            strcpy_s(&prlnbuf[*ip], sizeof(val_buf), val_buf);
            mode = ZP;
        }

        switch(prlnbuf[*ip]) {
        case '#':
            /* immediate */
            mode = IMM;
            (*ip)++;
            break;

        //case '<':
        //    /* zero page */
        //    mode = ZP | ZP_X | ZP_Y;
        //    (*ip)++;
        //    break;

        case '(':
        {
            int level = 0;
            int temp = *ip;
            int end = 0;

            while ('\0' != prlnbuf[temp]) {

                if ('(' == prlnbuf[temp]) {
                    level++;
                }

                if (')' == prlnbuf[temp]) {
                    end = temp;
                    level--;
                }

                temp++;
            }

            if (0 != level)
            {
                break;
            }

            prlnbuf[*ip] = '[';
            prlnbuf[end] = ']';

        }
        case '[':
            /* indirect */
            mode = ABS_IND | ABS_IND_X | ZP_IND | ZP_IND_X | ZP_IND_Y;
            (*ip)++;
            break;

        default:
            /* absolute */
            mode = ABS | ABS_X | ABS_Y;
            break;
        }

        /* get value */
        if (!evaluate(ip, 0))
            return (0);

        /* check addressing mode */
        code = 0;
        end = 0;
        pos = 0;

        while (!end) {
            c = prlnbuf[*ip];
            if (c == ';' || c == '\0')
                break;
            switch (toupper(c)) {
            case ',':        /* , = 5 */
                if (!pos)
                     pos = *ip;
                else {
                    end = 1;
                    break;
                }
                code++;
            case '+':        /* + = 4 */
                code++;
            case ')':        /* ] = 3 */
            case ']':        /* ] = 3 */
                code++;
                if (prlnbuf[*ip + 1] == '.') {
                    end = 1;
                    break;
                }
            case 'X':        /* X = 2 */
                code++;
            case 'Y':        /* Y = 1 */
                code++;
                code <<= 4;
            case ' ':
            case '\t':
                (*ip)++;
                break;
            default:
                code = 0xFFFFFF;
                end  = 1;
                break;
            }
        }

        /* absolute, zp, or immediate */
        if (code == 0x000000) {
            mode &= (ABS | ZP | IMM);
        }

        /* indirect */
        else if (code == 0x000030) {
            mode &= (ZP_IND | ABS_IND);        // ]
        }

        /* indexed modes */
        else if (code == 0x000510) {
            mode &= (ABS_Y | ZP_Y);            // ,Y
        }
        else if (code == 0x000520) {
            mode &= (ABS_X | ZP_X);            // ,X
        }
        else if (code == 0x005230) {
            mode &= (ZP_IND_X | ABS_IND_X);    // ,X]
        }
        else if (code == 0x003510) {
            mode &= (ZP_IND_Y);                // ],Y
        }
        else if (code == 0x000001) {
            mode &= (ZP_IND_Y);                // ].tag
            (*ip) += 2;

            /* get tag */
            tmp = value;

            if (!evaluate(ip, 0)) {
                return (0);
            }

            /* ok */
            auto_tag = 1;
            auto_tag_value = value;
            value = tmp;
        }
        /* indexed modes with post-increment */
        else if (code == 0x051440) {
            mode &= (ABS_Y | ZP_Y);            // ,Y++
            auto_inc = 0xC8;
        }
        else if (code == 0x052440) {
            mode &= (ABS_X | ZP_X);            // ,X++
            auto_inc = 0xE8;
        }
        else if (code == 0x351440) {
            mode &= (ZP_IND_Y);                // ],Y++
            auto_inc = 0xC8;
        }

        /* absolute, zp, or immediate (or error) */
        else {
            mode &= (ABS | ZP | IMM);
            if (pos) {
                *ip = pos;
            }
        }

        t_opcode* op_code = get_last_opcode();
        int op_value = last_op_value();

        /* absolute addressing is converted to zero page addressing */
        if ((mode & (ABS | ABS_X | ABS_Y)) && op_code && op_value >= 0 && op_value < 0x100)
        {
            do
            {
                if (undef)
                {
                    break;
                }

                if ((mode & ABS) && (op_code->flag & ZP))
                {
                    mode = ZP;
                    break;
                }

                else if ((mode & ABS_X) && (op_code->flag & ZP_X))
                {
                    mode = ZP_X;
                    break;
                }

                else if ((mode & ABS_Y) && (op_code->flag & ZP_Y))
                {
                    mode = ZP_Y;
                    break;
                }

            } while (0);
        }

        /* check value on last pass */
        if (pass == LAST_PASS) {
            /* zp modes */
            if (mode & (ZP | ZP_X | ZP_Y | ZP_IND | ZP_IND_X | ZP_IND_Y) & flag) {
                /* extension stuff */
                if (opext && !auto_inc) {
                    if (mode & (ZP_IND | ZP_IND_X | ZP_IND_Y))
                        error("Instruction extension not supported in indirect modes!");
                    if (opext == 'H')
                        value++;
                }
                /* check address validity */
                if ((value & 0xFFFFFF00) && ((value & 0xFFFFFF00) != machine->ram_base))
                    error("Incorrect zero page address!");
            }

            /* immediate mode */
            else if (mode & (IMM) & flag) {
                /* extension stuff */
                if (opext == 'L')
                    value = (value & 0xFF);
                else if (opext == 'H')
                    value = (value & 0xFF00) >> 8;
                else {
                    /* check value validity */
                    if ((value > 0xFF) && (value < 0xFFFFFF00))
                        error("Incorrect immediate value!");
                }
            }

            /* absolute modes */
            else if (mode & (ABS | ABS_X | ABS_Y | ABS_IND | ABS_IND_X) & flag) {
                /* extension stuff */
                if (opext && !auto_inc) {
                    if (mode & (ABS_IND | ABS_IND_X))
                        error("Instruction extension not supported in indirect modes!");
                    if (opext == 'H')
                        value++;
                }
                /* check address validity */
                if (value & 0xFFFF0000)
                    error("Incorrect absolute address!");
            }
        }
        break;
    }

    /* compare addressing mode */
    mode &= flag;
    if (!mode) {
        error("Incorrect addressing mode!");
        return (0);
    }

    /* skip spaces */
    while (isspace(prlnbuf[*ip]))
        (*ip)++;

    /* get last char */
    c = prlnbuf[*ip];

    /* check if it's what the user asked for */
    switch (last_char) {
    case ';':
        /* last operand */
        if (c != ';' && c != '\0') {
            error("Syntax error!");
            return (0);
        }
        (*ip)++;
        break;        

    case ',':
        /* need more operands */
        if (c != ',') {
            error("Operand missing!");
            return (0);
        }
        (*ip)++;
        break;        
    }

    /* ok */
    return (mode);
}


/* ----
 * getstring()
 * ----
 * get a string from prlnbuf
 */

int
getstring(int *ip, char *buffer, int size)
{
    unsigned char c;
    int i;

    /* skip spaces */
    while (isspace(baklnbuf[*ip]))
        (*ip)++;

    /* string must be enclosed */
    if (baklnbuf[(*ip)++] != '\"') {
        error("Incorrect string syntax!");
        return (0);
    }

    /* get string */
    i = 0;
    for (;;) {
        c = baklnbuf[(*ip)++];
        if (c == '\"')
            break;
        if (i >= size) {
            error("String too long!");
            return (0);
        }
        buffer[i++] = c;
    }

    /* end the string */
    buffer[i] = '\0';

    /* skip spaces */
    while (isspace(baklnbuf[*ip]))
        (*ip)++;

    /* ok */
    return (1);
}

